import enum
import re

# get_option_re = re.compile(r'get_option\s*\((\w+)\s*\)', re.MULTILINE)
# get_dependency_re = re.compile(r'dependency\s*\(\s*[\'"](\w+)[\'"]\s*\)', re.MULTILINE)
# find_library_re = re.compile(r"find_library\('(\w+)'", re.MULTILINE)
# find_program_re = re.compile(r"find_program\('(\w+)'", re.MULTILINE)
pattern_list_re = re.compile(r'dependencies:\s*\[\s*(.*?)\s*\]', re.MULTILINE)
pattern_var_re = re.compile(r"dependencies:\s*(\w+)", re.MULTILINE)
# import_re = re.compile(r"import\('(\w+)'", re.MULTILINE)
required_re = re.compile(r"required:\('(\w+)'", re.MULTILINE)

op_set = ['(', '+', '==', '!=', '<', '>', '<=', ">=", 'not', 'and', 'or']
METHOD_DEPS = {'gtkdoc':'gtkdoc', 'yelp':'yelp-tools'}


class ExpressionHandler(object):
    def __init__(self, options):
        self.options = options
        self.var_value_map = {}  # only record true, false, and options
        self.method_deps_map = METHOD_DEPS

    # for assignments
    def merge_variable(self, var1, var2):
        assert isinstance(var1, VariableState)
        var_name = var1.var_name
        var1.val = var2
        self.var_value_map[var_name] = var1

    def add_merge_variable(self, var1, var2):
        assert isinstance(var1, VariableState)
        var_name = var1.var_name
        # Note: only for array and concrete values
        original_var = var1.val
        if isinstance(original_var, ArrayValue):
            if isinstance(var2, ArrayValue):
                assigned_val = var2.get_val()
                if assigned_val is not None and assigned_val != '':
                    new_var = original_var.val[:]
                    new_var.extend(assigned_val)
                    original_var.val = new_var
                else:
                    new_var = original_var.val[:]
                    new_var.extend(var2)
                    original_var.val = new_var

        elif isinstance(original_var, ConcreteValue):
            if var2 is not None:
                assigned_val = var2.get_val()
                if assigned_val is not None and assigned_val != '':
                    new_var = CompositeValue(original_var, '+', assigned_val)
                    original_var.val = new_var
        else:
            print('unsolved += operations on unknown instances')
        self.var_value_map[var_name] = var1

    def get_right_value(self, right_str):
        right_strip_str = right_str.strip()
        if right_str.startswith('[') and right_str.endswith(']'):
            return self.get_array(right_str)
        if right_str.startswith('{') and right_str.endswith('}'):
            return self.get_dict(right_str)
        if self._check_is_str(right_str):
            return ConcreteValue(right_str)
        composite_val = self.get_composite_exp(right_strip_str)
        if composite_val is not None:
            return composite_val
        return self.get_atomic_value(right_strip_str)

    def get_left_value(self, left_str):
        return self.get_variable(left_str)

    def get_atomic_value(self, given_str):
        if 'true' == given_str or 'false' == given_str:
            return ConcreteValue(given_str)
        elif self._check_is_str(given_str) or given_str == '':
            return ConcreteValue(given_str)
        elif given_str.startswith('[') and given_str.endswith(']'):
            return self.get_array(given_str)
        elif self._check_is_func(given_str):
            return self.get_function_value(given_str)
        else:
            return self.get_variable_value(given_str)

    def _check_is_str(self, given_str):
        if not given_str.startswith('\'') or not given_str.endswith('\''):
            return False
        new_str = given_str.replace('\\\'', '')
        if new_str.count('\'') == 2:
            return True
        else:
            return False

    def _check_is_func(self, given_str):
        previous_char = None
        in_quote = False
        for current_char in given_str:
            if current_char == '\'':
                if previous_char != '\\':
                    in_quote = not in_quote
            if current_char == '.' or current_char == '(':
                if not in_quote:
                    return True
        return False




    def get_array(self, array_str):
        new_val = []
        new_array_str = array_str[1:len(array_str)-1]
        if ',' in array_str:
            new_val_strs = self.split_array_to_atomic_element(new_array_str)
            for new_val_single_str in new_val_strs:
                new_array_item = self.get_right_value(new_val_single_str)
                if new_array_item is not None:
                    new_val.append(new_array_item)
        else:
            new_array_item = self.get_right_value(new_array_str)
            if new_array_item is not None:
                new_val.append(new_array_item)
        return ArrayValue(new_val)

    def get_dict(self, dict_str):
        new_val = []
        new_array_str = dict_str[1:len(dict_str)-1]
        if ',' in dict_str:
            new_val_strs = self.split_array_to_atomic_element(new_array_str)
            for new_val_single_str in new_val_strs:
                new_array_item = self.get_right_value(new_val_single_str)
                if new_array_item is not None:
                    new_val.append(new_array_item)
        else:
            new_array_item = self.get_right_value(new_array_str)
            if new_array_item is not None:
                new_val.append(new_array_item)
        return ArrayValue(new_val)

    def is_assignment(self, line):
        left, operator, right = self.get_assignment(line)
        return operator is not None

    def get_assignment(self, line):
        left = None
        operator = None
        right = None

        previous_c = None
        next_c = None
        in_quote = False
        in_brackets = 0
        in_mid_brackets = 0
        in_big_brackets = 0
        index = 0
        while index < len(line):
            current_c = line[index]
            if index+1 < len(line):
                next_c = line[index+1]
            if current_c == '\'' and previous_c != '\\':
                in_quote = not in_quote
            elif current_c == '{' and not in_quote:
                in_big_brackets += 1
            elif current_c == '}' and not in_quote:
                in_big_brackets -= 1
            elif current_c == '[' and not in_quote and in_big_brackets == 0:
                in_mid_brackets += 1
            elif current_c == ']' and not in_quote and in_big_brackets == 0:
                in_mid_brackets -= 1
            elif current_c == '(' and not in_quote and in_big_brackets == 0 and in_mid_brackets == 0:
                in_brackets += 1
            elif current_c == ')' and not in_quote and in_big_brackets == 0 and in_mid_brackets == 0:
                in_brackets -= 1
            elif current_c == '=' and not in_quote  and in_big_brackets == 0 and in_mid_brackets == 0 and in_brackets == 0:
                if previous_c == '+' or previous_c == '-':
                    operator = previous_c + '='
                    left = line[0:index-1].strip()
                    right = line[index+1:].strip()
                    break
                elif previous_c != '=' and previous_c != '<' and previous_c != '>' and previous_c != '!' and next_c != '=':
                    operator = '='
                    left = line[0:index].strip()
                    right = line[index+1:].strip()
                    break
            previous_c = current_c
            index += 1
        return left, operator, right

    def split_array_to_atomic_element(self, array_str):
        # array_str = array_str[1:len(array_str)-1] # remove [ and ]
        return_strs = []
        previous_c = None
        in_quote = False
        in_brackets = 0
        index = 0
        previous_index = 0
        while index < len(array_str):
            current_c = array_str[index]
            if current_c == '\'' and previous_c != '\\':
                in_quote = not in_quote
            elif current_c == '(':
                in_brackets += 1
            elif current_c == ')':
                in_brackets -= 1
            elif current_c == ',' and in_brackets == 0 and not in_quote:
                if previous_index == 0:
                    new_str = array_str[previous_index:index]
                else:
                    new_str = array_str[previous_index+1:index]
                previous_index = index
                if new_str != '' and new_str != ',':
                    return_strs.append(new_str)
            previous_c = current_c
            index += 1
        extra_new_str = array_str[previous_index:]
        if extra_new_str != '' and extra_new_str != ',':
            return_strs.append(extra_new_str)
        return return_strs

    # Note: here returns the variable pointer
    def get_variable(self, variable_str):
        val_strip_str = variable_str.strip()
        if val_strip_str in self.var_value_map.keys():
            val = self.var_value_map[val_strip_str]
        else:
            val = VariableState(val_strip_str, EmptyValue())
            self.var_value_map[val_strip_str] = val
        return val

    # Note: here returns the value of the variable
    def get_variable_value(self, variable_str):
        val_strip_str = variable_str.strip()
        if val_strip_str in self.var_value_map.keys():
            val = self.var_value_map[val_strip_str].get_val()
        else:
            val = EmptyValue()
            self.var_value_map[val_strip_str] = VariableState(val_strip_str, val)
        return val

    def get_function_value(self, call_str): # TODO may return multiple values
        # dependency should be checked before get_option, since get_option may be contained in dependency
        keys = ['dependency', 'import', 'find_library', 'find_program', 'run_command']
        for key in keys:
            pattern = fr"{key}\s*\("
            matches = re.findall(pattern, call_str)
            for match in matches:
                return self.fun_get_dep_for_first_arg(call_str, match, key)

        keys_with_dependencies = ['declare_dependency', 'executable', 'compiles', 'static_library']
        for key in keys_with_dependencies:
            pattern = fr"{key}\s*\("
            matches = re.findall(pattern, call_str)
            for match in matches:
                return self.fun_get_dep_with_dependency_tag(call_str, key)

        # Note: only deal with spec method calls
        option_key = 'get_option'
        option_pattern = fr"{option_key}\s*\("
        matches = re.findall(option_pattern, call_str)
        for match in matches:
            return self.fun_get_option(call_str, match)


        if call_str.endswith('.found()'):
            return self.fun_is_found(call_str)

        for keyword in self.method_deps_map:
            if keyword in call_str:
                dep_str = self.method_deps_map[keyword]
                return DependencyValue(dep_str, 'dependency', None)
        return None

    def fun_is_found(self, get_option_str):
        match = get_option_str.replace('.found()', '').strip()
        return self.get_atomic_value(match)

    def fun_get_option(self, get_option_str, key_str):
        option_name = self._get_first_arg_endswith_brackets_or_comma(get_option_str, key_str)
        if option_name is None:
            print("error: no options found in a get_option command!")
            exit()
        new_var = self.get_right_value(option_name)
        option_val = None
        if isinstance(new_var, ConcreteValue):
            option_val = new_var.get_val()
        else:
            AssertionError()
        return OptionValue(option_val)

    #Note: for import, dependency, find_library, find_program
    def fun_get_dep_for_first_arg(self, given_str, key_str, tag):
        name = self._get_first_arg_endswith_brackets_or_comma(given_str, key_str)
        if name is None:
            print(f"error: no options found in an ${tag} command!")
            exit()
        return self._find_concrete_var_for_dep_funcs(name, given_str, tag)

    # def fun_import(self, import_str):
    #     import_name = self._get_first_arg_endswith_brackets_or_comma(import_str, 'import(')
    #     if import_name is None:
    #         print("error: no options found in an import command!")
    #         exit()
    #     return self._find_concrete_var_for_dep_funcs(import_name, import_str, 'import')


    # def fun_get_dependency(self, get_dependency_str):
    #     dependency_name = self._get_first_arg_endswith_brackets_or_comma(get_dependency_str, 'dependency(')
    #     if dependency_name is None:
    #         print("error: no options found in a dependency command!")
    #         exit()
    #     return self._find_concrete_var_for_dep_funcs(dependency_name, get_dependency_str, 'dependency')

    # def fun_find_library(self, find_library_str):
    #     library_name = self._get_first_arg_endswith_brackets_or_comma(find_library_str, 'find_library(')
    #     if library_name is None:
    #         print("error: no options found in a find_library command!")
    #         exit()
    #     return self._find_concrete_var_for_dep_funcs(library_name, find_library_str, 'find_library')
    #
    # def fun_find_program(self, find_program_str):
    #     program_name = self._get_first_arg_endswith_brackets_or_comma(find_program_str, 'find_program(')
    #     if program_name is None:
    #         print("error: no options found in a find_program command!")
    #         exit()
    #     return self._find_concrete_var_for_dep_funcs(program_name, find_program_str, 'find_program')

    # def fun_declare_dependency(self, declare_dependency):
    #     return self.fun_get_dep_with_dependency_tag(declare_dependency, 'declare_dependency')
    #
    # def fun_executable(self, executable_str):
    #     return self.fun_get_dep_with_dependency_tag(executable_str, 'executable')
    #
    # def fun_compiles(self, compiles_str):
    #     return self.fun_get_dep_with_dependency_tag(compiles_str, 'compiles')
    #
    # def fun_static_library(self, declare_dependency):
    #     return self.fun_get_dep_with_dependency_tag(declare_dependency, 'static_library')


    # 取消正则表达式，去除(与'中的,
    def fun_get_dep_with_dependency_tag(self, given_str, tag):
        # match_list = re.search(pattern_list_re, given_str)
        # match_var = re.search(pattern_var_re, given_str)
        # dep_array = []

        # if match_list:
        #     dependencies_str = match_list.group(1)
        #     dependencies_val = self.get_array(dependencies_str)
        #     all_deps_list = dependencies_val.get_contained_depends()
        #     if not all_deps_list:
        #         return None
        #     elif len(all_deps_list) == 1:
        #         return all_deps_list[0]
        #     else:
        #         return ArrayValue(all_deps_list)
        # elif match_var:
        #     dependencies_str = match_var.group(1)
        #     dependencies_val = self.get_variable_value(dependencies_str)
        #     all_deps_list = dependencies_val.get_contained_depends()
        #     if not all_deps_list:
        #         return None
        #     elif len(all_deps_list) == 1:
        #         return all_deps_list[0]
        #     else:
        #         return ArrayValue(all_deps_list)
        dependencies_str = self._find_str_after_dependencies(given_str)
        if dependencies_str is None:
            print(f"warning: no deps found in an ${tag} command!")
            return None
        dependencies_val = self.get_variable_value(dependencies_str)
        all_deps_list = []
        if dependencies_val is not None:
            all_deps_list = dependencies_val.get_contained_depends()
        if not all_deps_list:
            print(f"warning: deps analysis failed in an ${tag} command!")
            return None
        elif len(all_deps_list) == 1:
            return all_deps_list[0]
        else:
            return ArrayValue(all_deps_list)

    def _find_str_after_dependencies(self, given_str):
        index = given_str.find("dependencies:")
        if index == -1:
            return None
        start_index = index + len("dependencies:")
        stop_index = len(given_str) - 1
        current_index = start_index
        in_quote = False
        in_brackets = 0
        in_big_brackets = 0
        while current_index < len(given_str):
            current_char = given_str[current_index]
            if current_char == '\'' and given_str[current_index-1] != '\\':
                in_quote = not in_quote
            elif current_char == '(' and not in_quote:
                in_brackets += 1
            elif current_char == ')' and not in_quote:
                in_brackets -= 1
                if in_brackets == -1:
                    stop_index = current_index
                    break
            elif current_char == '[' and not in_quote and in_brackets <= 0:
                in_big_brackets += 1
            elif current_char == ']' and not in_quote and in_brackets <= 0:
                in_big_brackets -= 1
            elif current_char == ',' and not in_quote and in_brackets <= 0 and in_big_brackets <= 0:
                stop_index = current_index
                break
            current_index += 1
        return_str = given_str[start_index:stop_index]
        return return_str.strip()



    def _find_concrete_var_for_dep_funcs(self, option_name_str, original_total_str, tag):
        new_var = self.get_right_value(option_name_str)
        if isinstance(new_var, ConcreteValue):
            dep_val = new_var.get_concrete_val()
            required_val = None
            required_pattern = fr"required\s*:"
            matches = re.findall(required_pattern, original_total_str)
            for match in matches:
                required_name = self._get_first_arg_endswith_brackets_or_comma(original_total_str, match)
                if required_name is not None:
                    required_val = self.analyze_required(required_name)
                    break
            return DependencyValue(dep_val, tag, required_val)
        elif isinstance(new_var, ArrayValue):
            required_val = None
            required_name = self._get_first_arg_endswith_brackets_or_comma(original_total_str, 'required:')
            if required_name is not None:
                required_val = self.analyze_required(required_name)
            dep_str_set = new_var.get_val()
            dep_var_set = []
            for dep_str in dep_str_set:
                if isinstance(dep_str, ConcreteValue):
                    dep_var_set.append(DependencyValue(dep_str.get_concrete_val(), tag, required_val))
            if len(dep_var_set) == 0:
                return None
            elif len(dep_var_set) == 1:
                return dep_var_set[0]
            else:
                return ArrayValue(dep_var_set)
        else:
            AssertionError()



    def analyze_required(self, required_str):
        required_option_val = self.get_right_value(required_str)
        if isinstance(required_option_val, ConcreteValue):
            return None
        else:
            return required_option_val

    def get_composite_exp(self, expression):
        # if "('video/out/present_sync.c')" in expression:
        #     print(expression)
        parts = re.split(r'(\(|\)|\+|==|!=|<=|>=|<|>|\band\b|\bor\b|\bnot\b)', expression)
        composite_exp_tokens = [part.strip() for part in parts if part.strip()]
        composite_exp_tokens = self.merge_function_calls(composite_exp_tokens, expression)
        if len(composite_exp_tokens) <= 1:
            return None
        return self._parse_iteration(composite_exp_tokens)

    def merge_function_calls(self, tokens, expression):
        # if "gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())" in expression:
        #     print('x')
        stack = ''
        merged_tokens = []
        i = 0
        in_method = 0
        while i < len(tokens):
            token = tokens[i]
            if token == '(':
                if i != 0 and tokens[i - 1] not in {'not', 'and', 'or'}:
                    if in_method == 0:
                        # means it is method call
                        method_call = merged_tokens.pop()
                        stack = method_call + '('
                        if len(merged_tokens) > 0 and '.' == merged_tokens.pop():
                            stack = merged_tokens.pop() + '.' + stack
                    else:
                        stack += token
                    in_method += 1
                else:
                    merged_tokens.append(tokens[i])
            elif token == ')':
                if in_method > 0:
                    in_method -= 1
                    stack += tokens[i]
                    if in_method == 0:
                        merged_tokens.append(stack)
                        stack = ''

                else:
                    merged_tokens.append(tokens[i])
            elif token.startswith('.') and in_method == 0:
                merged_tokens[len(merged_tokens)-1] += token
            else:
                if in_method > 0:
                    stack += tokens[i]
                else:
                    merged_tokens.append(tokens[i])
            i = i + 1
        return merged_tokens

    def _reconstruct_tokens(self, tokens, start_index, exp, end_index):
        new_tokens = tokens[:start_index]
        new_tokens.append(exp)
        new_tokens.extend(tokens[end_index:])
        return new_tokens

    def _get_op_dict_single(self, tokens, given_op):
        op_position = []
        position = 0
        in_quota = False
        while position < len(tokens):
            token = tokens[position]
            if token == given_op and not in_quota:
                op_position.append(position)
            elif isinstance(token, str):
                index = 0
                while index < len(token):
                    current_c = token[index]
                    if current_c == '\'' and (index == 0 or not token[index-1] == '\\'):
                        in_quota = not in_quota
                    index += 1
            position += 1
        # filter the empty set
        return op_position

    # should return a 1-length array
    def _parse_iteration(self, tokens):
        if len(tokens) == 1:
            return self.get_atomic_value(tokens[0]) # the tokens can be reduced to length 1 in the iteration
        current_position = 0
        while current_position < len(tokens):
            current_token = tokens[current_position]
            if current_token == '(':
                bracket_num = 1
                inner_position = current_position + 1
                while inner_position < len(tokens):
                    inner_current_token = tokens[inner_position]
                    if inner_current_token == '(':
                        bracket_num += 1
                    elif inner_current_token == ')':
                        bracket_num -= 1
                        if bracket_num == 0:
                            new_exp = self._parse_iteration(tokens[current_position + 1:inner_position])
                            tokens = self._reconstruct_tokens(tokens, current_position, new_exp, inner_position + 1)
                    inner_position += 1
            current_position += 1

        # for other binary ops
        remaining_binary_op = ['==', '!=', '<', '>', '<=', '>=', '+']
        for binary_op in remaining_binary_op:
            while True:
                op_position = self._get_op_dict_single(tokens, binary_op)
                if len(op_position) > 0:
                    current_position = op_position[0]
                    new_tokens, new_position = self._parse_binary_exp(tokens, binary_op, current_position)
                    tokens = new_tokens
                else:
                    break

        # for not
        while True:
            op_position = self._get_op_dict_single(tokens, 'not')
            if len(op_position) > 0:
                current_position = op_position[0]
                new_tokens, new_position = self._parse_unary_exp(tokens, 'not', current_position)
                tokens = new_tokens
            else:
                break

        # for and and or
        remaining_binary_op = ['and', 'or']
        for binary_op in remaining_binary_op:
            while True:
                op_position = self._get_op_dict_single(tokens, binary_op)
                if len(op_position) > 0:
                    current_position = op_position[0]
                    new_tokens, new_position = self._parse_binary_exp(tokens, binary_op, current_position)
                    tokens = new_tokens
                else:
                    break

        if len(tokens) == 1:
            return tokens[0]
        else:
            return self._return_unknown_exp(tokens)

    def _return_unknown_exp(self, tokens):
        returned_exp = None
        for token in tokens:
            if isinstance(token, AbstractValue):
                if returned_exp is None:
                    returned_exp = token
                else:
                    returned_exp = CompositeValue(returned_exp, 'unknown', token)
        return returned_exp

    def _parse_unary_exp(self, tokens, current_token, position):
        another_token = tokens[position + 1]
        if isinstance(another_token, AbstractValue):
            val1 = another_token
        else:
            val1 = self.get_atomic_value(another_token)
        new_exp = CompositeValue(val1, current_token, None)
        tokens = self._reconstruct_tokens(tokens, position, new_exp, position + 2)
        position += 2
        return tokens, position

    def _parse_binary_exp(self, tokens, current_token, position):
        val1_token = tokens[position - 1]
        if isinstance(val1_token, AbstractValue) or val1_token is None:
            val1 = val1_token
        else:
            val1 = self.get_atomic_value(val1_token)
        val2_token = tokens[position + 1]
        if isinstance(val2_token, AbstractValue) or val2_token is None:
            val2 = val2_token
        else:
            val2 = self.get_atomic_value(val2_token)
        new_exp = CompositeValue(val1, current_token, val2)
        tokens = self._reconstruct_tokens(tokens, position - 1, new_exp, position + 2)
        position += 2
        return tokens, position


    def _get_first_arg_endswith_brackets_or_comma(self, given_str, tag):
        start = given_str.find(tag, 0)
        if start == -1:
            return None
        in_brackets = 1
        in_big_brackets = 0
        in_single_quote = False
        start += len(tag)
        return_arg = None
        end = start
        while end < len(given_str):
            c = given_str[end]
            if c == '(' and not in_single_quote:
                in_brackets += 1
            elif c == ')' and not in_single_quote:
                in_brackets -= 1
                if in_brackets == 0:
                    return_arg = given_str[start:end]
                    return return_arg.strip()
            elif c == '[' and not in_single_quote and in_brackets <= 1:
                in_big_brackets += 1
            elif c == ']' and not in_single_quote and in_brackets <= 1:
                in_big_brackets -= 1
            elif c == '\'' and (end == 0 or given_str[end-1] != '\\'):
                in_single_quote = not in_single_quote
            elif c == ',' and not in_single_quote and in_brackets == 1 and in_big_brackets == 0:
                return_arg = given_str[start:end]
                return return_arg.strip()
            end += 1
        return None

class AbstractValue(object):
    def __init__(self, val):
        self.val = val

    def get_val(self):
        return self.val

    def get_concrete_val(self):
        return self.val

    #TODO: to be enriched
    def generate_guard(self):
        return self
        # return self.get_simplified()

    def can_be_simplified(self):
        return True

    def get_simplified(self):
        return None

    def get_reverse_clone(self):
        return self

    def get_contained_options(self):
        return []

    def get_contained_depends(self):
        return []

    def to_simplified_string(self):
        return ''

class DictValue(AbstractValue):
    def __init__(self, val):# it is a dictionary
        super().__init__(val)

    def get_contained_options(self):
        contained_options = []
        if self.val is not None:
            for key in self.val:
                key_vals = self.val[key]
                for single_val in key_vals:
                    new_option = single_val.get_contained_options()
                    if new_option is not None:
                        contained_options.extend(new_option)
            if len(contained_options) > 0:
                return contained_options
        return contained_options

    def get_contained_depends(self):
        contained_depends = []
        if self.val is not None:
            for key in self.val:
                key_vals = self.val[key]
                for single_val in key_vals:
                    new_deps = single_val.get_contained_depends()
                    if new_deps is not None:
                        contained_depends.extend(new_deps)
        return contained_depends

class ArrayValue(AbstractValue):
    def __init__(self, val):
        super().__init__(val)

    def can_be_simplified(self):
        return True

    def get_contained_options(self):
        contained_options = []
        if self.val is not None:
            for single_val in self.val:
                new_option = single_val.get_contained_options()
                if new_option is not None:
                    contained_options.extend(new_option)
            if len(contained_options) > 0:
                return contained_options
        return contained_options

    def get_contained_depends(self):
        contained_depends = []
        if self.val is not None:
            for single_val in self.val:
                new_deps = single_val.get_contained_depends()
                if new_deps is not None:
                    contained_depends.extend(new_deps)
        return contained_depends


class ConcreteValue(AbstractValue):
    def __init__(self, val):
        super().__init__(val)

    def can_be_simplified(self):
        return True

    def get_reverse_clone(self):
        new_val = self.val
        if new_val == 'true':
            new_val = 'false'
        elif new_val == 'false':
            new_val = 'true'
        return ConcreteValue(new_val)

    def to_simplified_string(self):
        return self.val

class EmptyValue(AbstractValue):
    def __init__(self):
        super().__init__('')

    def can_be_simplified(self):
        return True


class OptionValue(AbstractValue):
    def __init__(self, val):
        super().__init__(val)

    def generate_guard(self):
        return self

    def can_be_simplified(self):
        return False

    def get_simplified(self):
        return self

    def get_contained_options(self):
        return [self]

    def to_simplified_string(self):# in default, use "+" to denote that this option is required
        # return "get_option(" + self.val + ")"
        return '+' + self.val.replace('\'', '')

class DependencyValue(AbstractValue):
    def __init__(self, val, type, required):
        super().__init__(val)
        self.type = type
        self.required = required

    def get_required(self):
        return self.required

    def can_be_simplified(self):
        return False

    def get_simplified(self):
        return self

    def get_contained_options(self):
        if self.required is not None:
            return self.required.get_contained_options()
        return []

    def get_contained_depends(self):
        return [self]

    def to_simplified_string(self):
        return self.type + "(" + self.val + ")"

class CompositeValue(AbstractValue):
    def __init__(self, val, operator, val2):
        super().__init__(val)
        self.operator = operator
        self.val2 = val2

    def can_be_simplified(self):
        if not isinstance(self.val, AbstractValue):
            return self.val2.can_be_simplified()
        if not isinstance(self.val2, AbstractValue):
            return self.val.can_be_simplified()
        return self.val.can_be_simplified() and self.val2.can_be_simplified()




    # a new exp is created here
    # def get_simplified(self):
    #     if not isinstance(self.val, AbstractValue):
    #         if self.val2.can_be_simplified():
    #             return None
    #         else:
    #             return self.val2.get_simplified()
    #     if not isinstance(self.val2, AbstractValue):
    #         if self.val.can_be_simplified():
    #             return None
    #         else:
    #             return self.val.get_simplified()
    #     else:
    #         if self.val2.can_be_simplified() and self.val.can_be_simplified():
    #             return None
    #         elif self.val2.can_be_simplified():
    #             return self.val.get_simplified()
    #         elif self.val.can_be_simplified():
    #             return self.val.get_simplified()
    #         else:
    #             return CompositeValue(self.val.get_simplified(), self.operator, self.val2.get_simplified())

    def get_reverse_clone(self):
        if self.operator == 'not':
            if isinstance(self.val, AbstractValue):
                return self.val
            else:
                AssertionError()
                return ConcreteValue(self.val)
        elif self.operator == 'and':
            return CompositeValue(self.val.get_reverse_clone(), 'or', self.val2.get_reverse_clone())
        elif self.operator == 'or':
            return CompositeValue(self.val.get_reverse_clone(), 'and', self.val2.get_reverse_clone())
        else:
            # can be further simplified for <,>,<=,>=
            return CompositeValue(self, 'not', None)

    def get_contained_options(self):
        contained_options = []
        if self.val is not None:
            contained_options.extend(self.val.get_contained_options())
        if self.val2 is not None:
            contained_options.extend(self.val2.get_contained_options())
        return contained_options

    def get_contained_depends(self):
        contained_deps = []
        if self.val is not None:
            contained_deps.extend(self.val.get_contained_depends())
        if self.val2 is not None:
            contained_deps.extend(self.val2.get_contained_depends())
        return contained_deps

    def to_simplified_string(self):
        if self.val is None and self.val2 is None:
            return None
        elif self.val is not None and self.val2 is None: # only for "not "
            val_simplified_str = self.val.to_simplified_string()
            if val_simplified_str is not None:
                if self.operator == 'not':
                    if isinstance(self.val, OptionValue):
                        return '-' + val_simplified_str.replace('+', '')
                    else:
                        return self.operator + ' ' + val_simplified_str
                else:
                    return val_simplified_str
            else:
                return None
        elif self.val2 is not None and self.val is None:
            return self.val2.to_simplified_string()
        elif self.operator == 'unknown':
            given_val = None
            if self.val is not None and not self.val.can_be_simplified():
                given_val = self.val
            elif self.val2 is not None and not self.val2.can_be_simplified():
                given_val = self.val2
            if given_val is None:
                return ''
            else:
                return given_val.to_simplified_string()
        else:
            val_simplified_str = self.val.to_simplified_string()
            val2_simplified_str = self.val2.to_simplified_string()
            if val2_simplified_str is not None and val_simplified_str is not None:
                return self._some_simplify_rules(val_simplified_str, self.operator, val2_simplified_str)
            elif val_simplified_str is not None:
                return val_simplified_str
            elif val2_simplified_str is not None:
                return val2_simplified_str
            else:
                return None

    def _some_simplify_rules(self, val_str, operator, val_str2):
        if operator == '==':
            if val_str == 'true':
                return val_str2
            elif val_str == 'false':
                return 'not ' + val_str2
            if val_str2 == 'true':
                return val_str
            elif val_str == 'false':
                return 'not ' + val_str
        elif operator == '!=':
            if val_str == 'true':
                return 'not ' + val_str2
            elif val_str == 'false':
                return val_str2
            if val_str2 == 'true':
                return 'not ' + val_str
            elif val_str == 'false':
                return val_str


        return '(' + val_str + ' ' + operator + ' ' + val_str2 + ')'

    @staticmethod
    def get_and_exp(exp1, exp2):
        return CompositeValue(exp1, 'and', exp2)



class GuardValue(CompositeValue):
    def __init__(self, val, operator, val2):
        super().__init__(val, operator, val2)


class VariableState(AbstractValue):
    def __init__(self, var_name, val):
        super().__init__(val)
        self.var_name = var_name


if __name__ == '__main__':
    # handler = ExpressionHandler([])
    # exp = handler.get_right_value("host_machine.cpu_family(a.get(x)).startswith('x86') and ( cc.get_id() != 'msvc' )")\
    s = "find_library('dl', required: false)"
    find_program_re = re.compile(r"find_library\('(\w+)'", re.MULTILINE)
    match = re.search(find_program_re, s)
    print(match)
